package biolabs.core

import java.util.logging.Logger

import scala.collection.mutable.{Map => MMap}

import jsr166y.ForkJoinPool
import biolabs.visual.{VisualizationConfiguration, VisualizationMethod}
import biolabs.visual.VisualizationConfiguration.{stageToItem, visualize}
import biolabs.visual.j2d.Java2D
import biolabs.visual.j3d.Java3D

object Application {
	val LOGGER = Logger.getLogger("biolabs")
}

/** Application is used to create experiments, keep track
  * of active experiments, configure default visualization
  * and takes care of interpreting user scripts.
  *
  * @throws RuntimeException if the initialization of the Scala
  * interpreter fails, either due to internal error or an error
  * in the `.biolabs.init` init script in user's home directory.
  *
  * @author Tomas Mikula
  */
class Application {

	private val interpreter = new ScriptInterpreter(this)
	private var listeners: List[ApplicationListener] = Nil
	private val experiments: MMap[String, Experiment] = MMap.empty
	private val ids = Stream.from(1).iterator
	private val experimentsGuard = new Object()
	private val fjPool = new ForkJoinPool()

	/** Map of default visualization configuration.
	  * Initially populated with some known visualizers.
	  */
	private var visConfMap: Map[VisualizationMethod[_], VisualizationConfiguration[_]] = Map(
		Java2D -> Java2D.configure(
			visualize [biolabs.model.Cell.Cell2D] using new biolabs.model.vis.j2d.GridShaped2DObjectVisualizer,
			visualize [biolabs.model.LiquidEnvironment2D] using new biolabs.model.vis.j2d.LiqEnv2DVisualizer
		),
		Java3D -> Java3D.configure(
			visualize [biolabs.model.Cell.Cell3D] using new biolabs.model.vis.j3d.GridShaped3DObjectVisualizer,
			visualize [biolabs.model.LiquidEnvironment3D] using new biolabs.model.vis.j3d.LiqEnv3DVisualizer
		)
	)

	private val expListener = new ExperimentListener() {
		override def experimentClosed(exp: Experiment) {
			experiments -= exp.getName()
		}
		override def experimentStarted(exp: Experiment) {/* do nothing */}
		override def experimentStopped(exp: Experiment) {/* do nothing */}
	}


	def getLogger() = Application.LOGGER

	def addListener(l: ApplicationListener) =
		listeners ::= l

	def removeListener(l: ApplicationListener) =
		listeners = listeners filterNot (_ == l)

	def interpret(script: String): InterpreterResult =
		interpreter.synchronized {
			return interpreter.interpret(script)
		}

	def evalExpr(expr: String): InterpreterResult =
		interpreter.synchronized {
			return interpreter.evalExpr(expr);
		}

	/** Creates a new experiment with a unique name. */
	def createExperiment(): Experiment =
		experimentsGuard.synchronized {
			val id = ids.find(i => !experiments.contains("experiment" + i)).get
			createManagedExperiment("experiment" + id)
		}

	/** Creates a new experiment with the given name.
	  * @throws ExperimentExistsException if an active experiment
	  * with the same name already exists.
	  */
	@throws(classOf[ExperimentExistsException])
	def createExperiment(name: String): Experiment =
		experimentsGuard.synchronized {
			if(experiments.contains(name))
				throw new ExperimentExistsException(name)
			return createManagedExperiment(name)
		}

	/** Returns the active experiment of the given name.
	  * @return the experiment of name `name`.
	  * If there is no such experiment, returns `null`.
	  */
	def getExperiment(name: String): Option[Experiment] =
		experimentsGuard.synchronized {
			experiments.get(name)
		}

	/** Returns the collection of all active experiments
	  * managed by this application.
	  */
	def getExperiments(): Iterable[Experiment] =
		experimentsGuard.synchronized {
			experiments.values
		}

	/** Must be called under the `experimentsGuard`. */
	private def createManagedExperiment(name: String): Experiment = {
		val exp = new Experiment(name, fjPool)
		experiments.put(name, exp) match {
			case None => // OK, do nothing
			case Some(e) => throw new AssertionError("Duplicate experiment " + name)
		}
		exp.addListener(expListener)
		for(l <- listeners)
			l.experimentCreated(exp)
		exp
	}

	/** Returns the default configuration for
	  * the given type of visualization method.
	  */
	def getDefaultVisualizationConfiguration[R](m: VisualizationMethod[R]) =
		visConfMap.get(m) match {
			case Some(cfg) => cfg.asInstanceOf[VisualizationConfiguration[R]]
			case None => m.emptyConfiguration()
		}

	/** Sets the default visualization configuration
	  * for the given visualization method.
	  */
	def setDefaultVisualizationConfiguration[R](m: VisualizationMethod[R], cfg: VisualizationConfiguration[R]) {
		visConfMap += (m -> cfg)
	}
}
